/* This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
 * 02111-1307, USA.
 *
 * http://www.gnu.org/copyleft/gpl.html
 */
package org.mmocore.network;

import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.channels.CancelledKeyException;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.WritableByteChannel;

/**
 * @author KenM
 * 
 */
public final class MMOConnection<T extends MMOClient<?>> {
	private final SelectorThread<T> _selectorThread;

	private final Socket _socket;

	private final InetAddress _address;

	private final ReadableByteChannel _readableByteChannel;

	private final WritableByteChannel _writableByteChannel;

	private final int _port;

	private final NioNetStackList<SendablePacket<T>> _sendQueue;

	private final SelectionKey _selectionKey;

	private SendablePacket<T> _closePacket;

	private ByteBuffer _readBuffer;

	private ByteBuffer _primaryWriteBuffer;

	private ByteBuffer _secondaryWriteBuffer;

	private boolean _pendingClose;

	private T _client;

	public MMOConnection(final SelectorThread<T> selectorThread,
			final Socket socket, final SelectionKey key) {
		_selectorThread = selectorThread;
		_socket = socket;
		_address = socket.getInetAddress();
		_readableByteChannel = socket.getChannel();
		_writableByteChannel = socket.getChannel();
		_port = socket.getPort();
		_selectionKey = key;

		_sendQueue = new NioNetStackList<SendablePacket<T>>();
	}

	final void setClient(final T client) {
		_client = client;
	}

	public final T getClient() {
		return _client;
	}

	public final void sendPacket(final SendablePacket<T> sp) {
		sp._client = _client;

		synchronized (getSendQueue()) {
			if (!_pendingClose) {
				_sendQueue.addLast(sp);
			}
		}

		if (!_sendQueue.isEmpty()) {
			try {
				_selectionKey.interestOps(_selectionKey.interestOps()
						| SelectionKey.OP_WRITE);
			} catch (CancelledKeyException e) {
				// ignore
			}
		}
	}

	final SelectionKey getSelectionKey() {
		return _selectionKey;
	}

	public final InetAddress getInetAddress() {
		return _address;
	}

	public final int getPort() {
		return _port;
	}

	final void close() throws IOException {
		_socket.close();
	}

	final int read(final ByteBuffer buf) throws IOException {
		return _readableByteChannel.read(buf);
	}

	final int write(final ByteBuffer buf) throws IOException {
		return _writableByteChannel.write(buf);
	}

	final void createWriteBuffer(final ByteBuffer buf) {
		if (_primaryWriteBuffer == null) {
			_primaryWriteBuffer = _selectorThread.getPooledBuffer();
			_primaryWriteBuffer.put(buf);
		} else {
			final ByteBuffer temp = _selectorThread.getPooledBuffer();
			temp.put(buf);

			final int remaining = temp.remaining();
			_primaryWriteBuffer.flip();
			final int limit = _primaryWriteBuffer.limit();

			if (remaining >= _primaryWriteBuffer.remaining()) {
				temp.put(_primaryWriteBuffer);
				_selectorThread.recycleBuffer(_primaryWriteBuffer);
				_primaryWriteBuffer = temp;
			} else {
				_primaryWriteBuffer.limit(remaining);
				temp.put(_primaryWriteBuffer);
				_primaryWriteBuffer.limit(limit);
				_primaryWriteBuffer.compact();
				_secondaryWriteBuffer = _primaryWriteBuffer;
				_primaryWriteBuffer = temp;
			}
		}
	}

	final boolean hasPendingWriteBuffer() {
		return _primaryWriteBuffer != null;
	}

	final void movePendingWriteBufferTo(final ByteBuffer dest) {
		_primaryWriteBuffer.flip();
		dest.put(_primaryWriteBuffer);
		_selectorThread.recycleBuffer(_primaryWriteBuffer);
		_primaryWriteBuffer = _secondaryWriteBuffer;
		_secondaryWriteBuffer = null;
	}

	final void setReadBuffer(final ByteBuffer buf) {
		_readBuffer = buf;
	}

	final ByteBuffer getReadBuffer() {
		return _readBuffer;
	}

	public final boolean isClosed() {
		return _pendingClose;
	}

	final NioNetStackList<SendablePacket<T>> getSendQueue() {
		return _sendQueue;
	}

	final SendablePacket<T> getClosePacket() {
		return _closePacket;
	}

	public final void close(final SendablePacket<T> sp) {
		synchronized (getSendQueue()) {
			if (!_pendingClose) {
				_pendingClose = true;
			}

			_sendQueue.clear();
		}

		try {
			_selectionKey.interestOps(_selectionKey.interestOps()
					& ~SelectionKey.OP_WRITE);
		} catch (CancelledKeyException e) {
			// ignore
		}

		_closePacket = sp;
		_selectorThread.closeConnection(this);
	}

	final void releaseBuffers() {
		if (_primaryWriteBuffer != null) {
			_selectorThread.recycleBuffer(_primaryWriteBuffer);
			_primaryWriteBuffer = null;

			if (_secondaryWriteBuffer != null) {
				_selectorThread.recycleBuffer(_secondaryWriteBuffer);
				_secondaryWriteBuffer = null;
			}
		}

		if (_readBuffer != null) {
			_selectorThread.recycleBuffer(_readBuffer);
			_readBuffer = null;
		}
	}
}
